part of '../flutter_ns_ble.dart';

final _events = providePeripheralEvent();

class Peripheral with ChangeNotifier {
  final String? name;
  final String address;

  final _methods = PeripheralMethod();

  static late final logEvent = provideLogEvent();

  Peripheral({required this.name, required this.address}) {
    _methods.ensureInitialized(address);
  }

  late final Stream<PeripheralState> _state = _events
      .where((e) => e is PeripheralStateEvent && e.address == address)
      .cast<PeripheralStateEvent>()
      .map((e) => e.state);

  late ValueStream<PeripheralState> state = _state.shareValueSeeded(
    PeripheralState.disconnected,
  );

  PeripheralType get type {
    if (info.value.hardwareVersion.isEmpty) {
      return PeripheralType.fromString(name ?? "");
    } else {
      return PeripheralType.fromString(info.value.hardwareVersion);
    }
  }

  late Stream<PeripheralNotifyEvent> notifyEvent =
      _events.where((e) {
        
        return e is PeripheralNotifyEvent && e.address == address;
      }).cast<PeripheralNotifyEvent>();

  Stream<EcgData<List<int>>> get ecgData => notifyEvent
      .where((e) => e.cmd == CMD.ecgData)
      .map((e) => e.data)
      .whereNotNull()
      .map((e) {
        var index = Uint8List.fromList([
          ...e.sublist(0, 3),
          0,
        ]).buffer.asByteData().getInt32(0, Endian.little);
        var data = e.sublist(4);
        var byteData = data.buffer.asByteData();
        var list = List<int>.empty(growable: true);
        for (var i = 0; i < byteData.lengthInBytes / 2; i++) {
          var num = byteData.getInt16(i * 2, Endian.little);
          list.add(num);
        }
        return EcgData(order: index, data: list);
      });

  PeripheralState get currentState => state.value;

  Future<bool> connect() async {
    var result = await _methods.connect(address);
    await Future.delayed(Duration(milliseconds: 100));
    await _methods.write(
      address,
      Platform.isAndroid ? CMD.android : CMD.ios,
      null,
    );
    await requestLeadState();
    await requestPeripheralInfo();
    return result;
  }

  Future<bool> disconnect() async {
    return _methods.disconnect(address);
  }

  late final Stream<bool> _leadState = _buildDefaultNotifyStream(
    cmd: CMD.ecgStatus,
    converter: (data) => data.first == 1,
    disconnectedValue: false,
  );

  ValueStream<bool> get leadState => _leadState.shareValueSeeded(false);

  late final Stream<F1LeadState> _f1LeadState = _buildDefaultNotifyStream(
    cmd: CMD.ecgStatus,
    converter: (data) => F1LeadState.fromBlePacket(data),
    disconnectedValue: F1LeadState(),
  );

  ValueStream<F1LeadState> get f1LeadState =>
      _f1LeadState.shareValueSeeded(F1LeadState());

  /// 获取最新的导联状态
  Future<bool> requestLeadState() async {
    return await _request(cmd: CMD.ecgStatus, response: _leadState);
  }

  late final Stream<PeripheralInfo> _info = _buildDefaultNotifyStream(
    cmd: CMD.info,
    converter: PeripheralInfo.fromByteArray,
    disconnectedValue: PeripheralInfo(),
  );

  ValueStream<PeripheralInfo> get info =>
      _info.shareValueSeeded(PeripheralInfo());

  /// 获取最新的设备信息
  Future<PeripheralInfo> requestPeripheralInfo() async {
    return _request(cmd: CMD.info, response: _info);
  }

  /// 开启ECG
  Future<bool> startECG({
    Duration duration = const Duration(seconds: 30),
    bool hardwareFilter = true,
  }) async {
    Completer<bool> completer = Completer();
    var ss = notifyEvent
        .where((e) => e.cmd == CMD.ecgCtl)
        .map((e) => e.data)
        .whereNotNull()
        .listen((data) {
          completer.complete(AskCode.fromByteArray(data) == AskCode.success);
        });

    await _methods.write(
      address,
      CMD.ecgCtl,
      Uint8List.fromList([
        0x01,
        ...duration.inSeconds.toByteArray16(),
        hardwareFilter ? 0x01 : 0x00,
      ]),
    );
    var result = await completer.future;
    ss.cancel();
    return result;
  }

  /// 停止ECG
  Future<bool> stopECG() async {
    Completer<bool> completer = Completer();
    var ss = notifyEvent
        .where((e) => e.cmd == CMD.ecgCtl)
        .map((e) => e.data)
        .whereNotNull()
        .listen((data) {
          completer.complete(AskCode.fromByteArray(data) == AskCode.success);
        });

    await _methods.write(
      address,
      CMD.ecgCtl,
      Uint8List.fromList([0x02, 0x00, 0x00, 0x00]),
    );
    var result = await completer.future;
    ss.cancel();
    return result;
  }

  /// B1 开始

  /// B1设备状态
  late final Stream<B1DeviceState> _b1DeviceState = _buildDefaultNotifyStream(
    cmd: CMD.measStatus,
    converter: (data) => B1DeviceState.fromByteArray(data),
    disconnectedValue: B1DeviceState.idle,
  );

  ValueStream<B1DeviceState> get b1DeviceState =>
      _b1DeviceState.shareValueSeeded(B1DeviceState.idle);

  Future<B1DeviceState> requestB1DeviceState() async {
    return await _request(cmd: CMD.measStatus, response: _b1DeviceState);
  }

  /// B1设备同步状态
  late final Stream<int> _b1SyncDataTime = _buildDefaultNotifyStream(
    cmd: CMD.timeCache,
    converter: (data) {
      /// 新机器第一次回是0xff
      if (data.contentEquals([0xff, 0xff, 0xff, 0xff])) {
        return 0;
      }
      var builder = BytesBuilder()..add(data);
      var timestamp =
          builder.toBytes().buffer.asByteData().getInt32(0, Endian.little) *
          1000;
      return timestamp;
    },
    disconnectedValue: 0,
  );

  ValueStream<int> get b1SyncDataTime => _b1SyncDataTime.shareValueSeeded(0);

  Future<int> getB1SyncDataTime() async {
    return await _request(
      cmd: CMD.timeCache,
      value: [0],
      response: _b1SyncDataTime,
    );
  }

  Future<int> setB1SyncDataTime() async {
    final bytesBuilder = BytesBuilder();
    var byteData = ByteData(4);
    byteData.setInt32(
      0,
      DateTime.now().millisecondsSinceEpoch ~/ 1000,
      Endian.little,
    );
    bytesBuilder.addByte(1);
    bytesBuilder.add(byteData.buffer.asUint8List().sublist(0, 4));
    return await _request(
      cmd: CMD.timeCache,
      value: bytesBuilder.toBytes(),
      response: _b1SyncDataTime,
    );
  }

  Future<int> clearB1SyncDataTime() async {
    return await _request(
      cmd: CMD.timeCache,
      value: [1, 0, 0, 0, 0],
      response: _b1SyncDataTime,
    );
  }

  Future<AskCode> startB1Holter({int seconds = 24 * 3600}) async {
    final bytesBuilder = BytesBuilder();
    bytesBuilder.addByte(1);
    var byteData = ByteData(4);
    byteData.setInt32(0, seconds, Endian.little);
    bytesBuilder.add(byteData.buffer.asUint8List().sublist(0, 3));
    var resp = await _request(
      cmd: CMD.measTag,
      value: bytesBuilder.toBytes(),
      response: _askCodeResponse(CMD.measTag),
    );
    await setB1SyncDataTime();
    return resp;
  }

  Future<AskCode> stopB1Holter() async {
    var resp = await _request(
      cmd: CMD.measTag,
      value: [0],
      response: _askCodeResponse(CMD.measTag),
    );
    return resp;
  }

  Future waitB1SyncDataCompleted() async {
    await notifyEvent.where((e) => e.cmd == CMD.measTag).first;
  }

  /// B1 结束

  Stream<AskCode> _askCodeResponse(CMD cmd) {
    return notifyEvent
        .where((e) => e.cmd == cmd)
        .map((e) => e.data)
        .whereNotNull()
        .map(AskCode.fromByteArray);
  }

  Future<T> _request<T>({
    required CMD cmd,
    List<int>? value,
    required Stream<T> response,
  }) async {
    Completer<T> completer = Completer();
    var ss = response.listen((data) {
      completer.complete(data);
    });

    await _methods.write(
      address,
      cmd,
      value != null ? Uint8List.fromList(value) : null,
    );
    try {
      var result = await completer.future.timeout(Duration(seconds: 5));
      return result;
    } on TimeoutException catch (e) {
      print("$this request $cmd timeout");
      rethrow;
    } finally {
      ss.cancel();
    }
  }

  Stream<T> _buildDefaultNotifyStream<T>({
    required CMD cmd,
    required T Function(Uint8List data) converter,
    required T disconnectedValue,
  }) {
    return CombineLatestStream.combine2<T, PeripheralState, T>(
      notifyEvent
          .where((e) {
            return e.cmd == cmd;
          })
          .map((e) => e.data)
          .whereNotNull()
          .map(converter),
      state,
      (value, state) {
        return state == PeripheralState.ready ||
                state == PeripheralState.connected
            ? value
            : disconnectedValue;
      },
    ).asBroadcastStream();
  }
}

extension IntExt on int {
  Uint8List toByteArray16({Endian endian = Endian.little}) {
    final data = ByteData(4);
    data.setInt32(0, this, endian);
    return data.buffer.asUint8List(0, 2);
  }

  Uint8List toByteArray24({Endian endian = Endian.little}) {
    final data = ByteData(4);
    data.setInt32(0, this, endian);
    return data.buffer.asUint8List(0, 3);
  }
}
